/*
 * Copyright 2009 Thor Hovden
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the
 * License. 
 */
package eviva.structure;

import java.util.ArrayList;

import eviva.exceptions.MissionException;

/**
 * Allows programmers to describe execution contexts that can be output beside error messages and aid in diagnose.
 * An object instance is created in main method or a similar place, and passed around. Each significant method sets
 * a mission, a short description of its purpose. Before throwing an exception, tell missionControl to abort().
 * Before ending method, tell missionControl that your mission is complete().
 * <p>
 * During application logging, you can output mission log from missionControl. This feature is the main reason
 * behind constructing this class. A special exception, MissionException, can be used as superclass for your
 * application level exceptions. It will produce a neat output with mission log followed by exception message.
 * <p>
 * This class implements a stack using ArrayList in order to be used for Java 5. With Java 6, we could have used
 * ArrayDeque instead.
 * <p>
 * Future expansion: output [milliseconds of execution since last log entry] after each mission log entry output.
 * 
 * @see MissionException
 * @author Thor Hovden
 */
public class MissionControl {
	private ArrayList<Mission> context = new ArrayList<Mission>();
	private Character indentationChar = ' ';

	public MissionControl() {
	}

	public void configureIndentation(Character i) {
		indentationChar = i;
	}

	/**
	 * Logs a new mission. Typically called when a method starts.
	 * 
	 * @see #complete
	 * @see #abort
	 * @param level
	 * @param missionDescription
	 */
	public Mission set(Integer level, String missionDescription) {
		Mission s = new Mission(level, missionDescription);
		context.add(s);
		return s;
	}

	/**
	 * Log a comment
	 * 
	 * @param m MissionControl.Mission object
	 * @param comment
	 */
	public void log(Mission m, String comment) {
		Mission s = new Mission(m.level, "Comment: " + comment);
		context.add(s);
	}

	/**
	 * Log a comment. This is a shortcut to {@link #log(Mission, String)}
	 * 
	 * @param m
	 * @param e
	 */
	public void log(Mission m, Exception e) {
		log(m, e.getMessage());
	}

	/**
	 * Log a comment. This is a shortcut to {@link #log(Mission, String)}
	 * 
	 * @param m
	 * @param s
	 */
	public void ignore(Mission m, String s) {
		log(m, s);
	}

	/**
	 * Log a comment. This is a shortcut to {@link #log(Mission, Exception)}
	 * 
	 * @param m
	 * @param e
	 */
	public void ignore(Mission m, Exception e) {
		log(m, e.getMessage());
	}

	/**
	 * Removes mission from log. This must be called for each mission in LIFO order, to end it. Typically called
	 * when a method ends.
	 */
	public void complete(Mission m) {
		if (!context.contains(m)) {
			System.err.println("Mission control could not mark mission " + m
					+ " complete as it seems vanished from our records.\n" + "Current mission log:\n"
					+ getMissionLog());
		}
		context.add(new Mission(m.level, m.description + " - Completed"));
	}

	/**
	 * Logs a mission abort. Typically called from before throwing exception.
	 */
	public void abort(Mission m, String information) {
		Mission aborted = new Mission(m.level, "Aborted with information: " + information);
		context.add(aborted);
	}

	/**
	 * Logs a mission abort. Typically called from before throwing exception. This is a shortcut to
	 * {@link #abort(Mission, String)}
	 */
	public void abort(Mission m, Exception e) {
		abort(m, e.getMessage());
	}

	/**
	 * Get a printable representation of the mission log
	 * 
	 * @return mission log as a String
	 */
	public String getMissionLog() {
		StringBuffer sb = new StringBuffer();
		for (Mission m : context) {
			sb.append(indentation(m.level));
			sb.append(m.description);
			sb.append('\n');
		}
		return sb.toString();
	}

	/**
	 * Create a String of indentation characters, to specified length
	 * 
	 * @param length
	 * @return indentation String
	 */
	private String indentation(int length) {
		if (indentationChar == null) {
			return "";
		}
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < length; i++) {
			buf.append(indentationChar);
		}
		return buf.toString();
	}

	public class Mission {
		String description;
		Integer level;

		/**
		 * Add a mission
		 * 
		 * @param level value used for indentation and together with description as identification of a unique
		 *            mission
		 * @param description
		 */
		public Mission(Integer level, String description) {
			this.description = description;
			this.level = level;
		}

		public String toString() {
			return description;
		}

		public boolean equals(Object o) {
			return (o != null && o instanceof Mission && ((Mission) o).level != null
					&& ((Mission) o).level.equals(level) && ((Mission) o).description != null && ((Mission) o).description
					.equals(description));
		}
	}

}
